{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "eIrvnAbGZ1wP"
      },
      "source": [
        "##### Copyright 2019 The TensorFlow Authors."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "cellView": "form",
        "id": "_A4IPZ-WZ9H7"
      },
      "outputs": [],
      "source": [
        "#@title Licensed under the Apache License, Version 2.0 (the \"License\");\n",
        "# you may not use this file except in compliance with the License.\n",
        "# You may obtain a copy of the License at\n",
        "#\n",
        "# https://www.apache.org/licenses/LICENSE-2.0\n",
        "#\n",
        "# Unless required by applicable law or agreed to in writing, software\n",
        "# distributed under the License is distributed on an \"AS IS\" BASIS,\n",
        "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
        "# See the License for the specific language governing permissions and\n",
        "# limitations under the License."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "WpaDQG8VaYFO"
      },
      "source": [
        "# Маскирование и дополнение данных с Keras\n",
        "\n",
        "<table class=\"tfo-notebook-buttons\" align=\"left\">\n",
        "  <td>\n",
        "    <a target=\"_blank\" href=\"https://www.tensorflow.org/guide/keras/masking_and_padding\">\n",
        "    <img src=\"https://www.tensorflow.org/images/tf_logo_32px.png\" />\n",
        "    Смотрите на TensorFlow.org</a>\n",
        "  </td>\n",
        "  <td>\n",
        "    <a target=\"_blank\" href=\"https://colab.research.google.com/github/tensorflow/docs-l10n/blob/master/site/ru/guide/keras/masking_and_padding.ipynb\">\n",
        "    <img src=\"https://www.tensorflow.org/images/colab_logo_32px.png\" />\n",
        "    Запустите в Google Colab</a>\n",
        "  </td>\n",
        "  <td>\n",
        "    <a target=\"_blank\" href=\"https://github.com/tensorflow/docs-l10n/blob/master/site/ru/guide/keras/masking_and_padding.ipynb\">\n",
        "    <img src=\"https://www.tensorflow.org/images/GitHub-Mark-32px.png\" />\n",
        "    Изучайте код на GitHub</a>\n",
        "  </td>\n",
        "  <td>\n",
        "    <a target=\"_blank\" href=\"https://storage.googleapis.com/tensorflow_docs/docs-l10n/site/ru/guide/keras/masking_and_padding.ipynb\">\n",
        "    <img src=\"https://www.tensorflow.org/images/download_logo_32px.png\" />\n",
        "    Скачайте ноутбук</a>\n",
        "  </td>\n",
        "</table>"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "fj66ZXAzrJC2"
      },
      "source": [
        "Note: Вся информация в этом разделе переведена с помощью русскоговорящего Tensorflow сообщества на общественных началах. Поскольку этот перевод не является официальным, мы не гарантируем что он на 100% аккуратен и соответствует [официальной документации на английском языке](https://www.tensorflow.org/?hl=en). Если у вас есть предложение как исправить этот перевод, мы будем очень рады увидеть pull request в [tensorflow/docs](https://github.com/tensorflow/docs) репозиторий GitHub. Если вы хотите помочь сделать документацию по Tensorflow лучше (сделать сам перевод или проверить перевод подготовленный кем-то другим), напишите нам на [docs-ru@tensorflow.org list](https://groups.google.com/a/tensorflow.org/forum/#!forum/docs-ru)."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "QGJH5EKYoSHZ"
      },
      "source": [
        "## Установка"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "wJEBe8hTlB6W"
      },
      "outputs": [],
      "source": [
        "from __future__ import absolute_import, division, print_function, unicode_literals\n",
        "\n",
        "import numpy as np\n",
        "\n",
        "try:\n",
        "  # %tensorflow_version only exists in Colab.\n",
        "  %tensorflow_version 2.x\n",
        "except Exception:\n",
        "  pass\n",
        "import tensorflow as tf\n",
        "\n",
        "from tensorflow.keras import layers"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "5ShanCQ_pSPO"
      },
      "source": [
        "## Дополнение последовательных данных\n",
        "\n",
        "При обработке последовательных данных очень часто отдельные примеры имеют разную длину. Рассмотрим следующий пример (текст разбитый на слова):\n",
        "\n",
        "```\n",
        "[\n",
        "  [\"The\", \"weather\", \"will\", \"be\", \"nice\", \"tomorrow\"],\n",
        "  [\"How\", \"are\", \"you\", \"doing\", \"today\"],\n",
        "  [\"Hello\", \"world\", \"!\"]\n",
        "]\n",
        "```\n",
        "\n",
        "После просмотра словаря данные могут быть векторизированы как целые числа, напр.:\n",
        "\n",
        "```\n",
        "[\n",
        "  [83, 91, 1, 645, 1253, 927],\n",
        "  [73, 8, 3215, 55, 927],\n",
        "  [71, 1331, 4231]\n",
        "]\n",
        "```\n",
        "\n",
        "Данные являются двумерным списком где длина отдельных примеров равна 6, 5, и 3 соответственно. Поскольку входные данные для модели глубокого обучения должны быть тензором одинаковой размерности(напр. `(batch_size, 6, vocab_size)` в этом случае), элементы которые короче самого длинного элемента нужно дополнить некоторым значением по умолчанию (в качестве альтернативы, можно также обрезать длинные элементы перед заполнением коротких).\n",
        "\n",
        "Keras предоставляет API позволяющий легко обрезать и дополнять последовательности до общей длины: `tf.keras.preprocessing.sequence.pad_sequences`."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "xI-lHnyxfa2T"
      },
      "outputs": [],
      "source": [
        "raw_inputs = [\n",
        "  [83, 91, 1, 645, 1253, 927],\n",
        "  [73, 8, 3215, 55, 927],\n",
        "  [711, 632, 71]\n",
        "]\n",
        "\n",
        "# По умолчанию это заполнится нулями; настраивать можно с помощью\n",
        "# параметра \"value\".\n",
        "# Заметьте что вы можете \"пре\" заполнить (добавив нули в начале) или\n",
        "# \"пост\" заполнить (добавив в конце).\n",
        "# Мы рекомендуем использовать \"пост\" заполнение при работе со слоями RNN\n",
        "# (для того чтобы иметь возможность использовать \n",
        "# CuDNN реализацию слоев).\n",
        "padded_inputs = tf.keras.preprocessing.sequence.pad_sequences(raw_inputs,\n",
        "                                                              padding='post')\n",
        "\n",
        "print(padded_inputs)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "HyHf90yAqkMr"
      },
      "source": [
        "## Masking"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "o16pUIBLgc_Q"
      },
      "source": [
        "Сейчас когда у всех примеров одна длина, модели нужно сообщить, что некоторая часть данных на самом деле является дополнением и должна быть проигнорирована. Это можно сделать с помощью <b>маски</b>.\n",
        "\n",
        "Существует три способа ввода масок в модели Keras.:\n",
        "\n",
        "- Добавить слой `keras.layers.Masking`.\n",
        "- Добавить `mask_zero=True` в конфигурацию слоя `keras.layers.Embedding`.\n",
        "- Передать аргумент `mask` вручную при вызове слоев с поддержкой этого аргумента (напр. слои RNN)."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "f6QMNceyf1GD"
      },
      "source": [
        "## Слои, генерирующие маски: `Embedding` и `Masking`\n",
        "\n",
        "Под капотом эти слои создадут тензор-маску (2D тензор размерности `(batch, sequence_length)`), и приложат его к тензору возвращаемому на выходе слоем `Masking` или `Embedding`."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "rYXQ589PkC0P"
      },
      "outputs": [],
      "source": [
        "embedding = layers.Embedding(input_dim=5000, output_dim=16, mask_zero=True)\n",
        "masked_output = embedding(padded_inputs)\n",
        "\n",
        "print(masked_output._keras_mask)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "-0VVscXQm1D1"
      },
      "outputs": [],
      "source": [
        "masking_layer = layers.Masking()\n",
        "# Смоделируем поиск вложенния, расширив 2D входные данные до 3D,\n",
        "# с размерностью вложения равной 10.\n",
        "unmasked_embedding = tf.cast(\n",
        "    tf.tile(tf.expand_dims(padded_inputs, axis=-1), [1, 1, 10]),\n",
        "    tf.float32)\n",
        "\n",
        "masked_embedding = masking_layer(unmasked_embedding)\n",
        "print(masked_embedding._keras_mask)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "RL2vsiCBmVck"
      },
      "source": [
        "Как видно из напечатанного результата, маска это двумерный булев тензор рамерности `(batch_size, sequence_length)`, где каждая отдельная запись `False` указывает на то, что соответствующий временной шаг нужно игнорировать во время работы."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "s4jsu6oTrl2f"
      },
      "source": [
        "## Распространение маски в Functional API и Sequential API"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "0KgNt7fvm0Jx"
      },
      "source": [
        "При использовании Functional API или Sequential API, маска генерируемая слоем `Embedding` или `Masking` распространится по всей сети, по всем слоям которые могут ее использовать (напр. слои RNN). Keras автоматически извлечет маску соответствующую входу и передаст ее любому слою который знает, как ее использовать.\n",
        "\n",
        "Заметьте что в методе `call` наследованной модели или слоя, маски не распространяются автоматически, поэтому вам нужно вручную передать аргумент `mask` в каждый слой, который в этом нуждается. См. секцию ниже для деталей.\n",
        "\n",
        "Например, в следующей Sequential модели, слой `LSTM` автоматически получит маску, что означает, что он проигнорирует добавленные значения:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "zfkxyf7yVyxJ"
      },
      "outputs": [],
      "source": [
        "model = tf.keras.Sequential([\n",
        "  layers.Embedding(input_dim=5000, output_dim=16, mask_zero=True),\n",
        "  layers.LSTM(32),\n",
        "])"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "UqZeTeEhWHLE"
      },
      "source": [
        "Это так же относится к следующей модели Functional API:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "SYaVl6WSWJal"
      },
      "outputs": [],
      "source": [
        "inputs = tf.keras.Input(shape=(None,), dtype='int32')\n",
        "x = layers.Embedding(input_dim=5000, output_dim=16, mask_zero=True)(inputs)\n",
        "outputs = layers.LSTM(32)(x)\n",
        "\n",
        "model = tf.keras.Model(inputs, outputs)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "crxoxoRDWg8t"
      },
      "source": [
        "## Передача тензоров масок напрямую в слои"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "UfvcEl20XRYA"
      },
      "source": [
        "У слоев которые могут обрабатывать маски (такие как `LSTM`) есть аргумент `mask` в их методе `__call__`.\n",
        "\n",
        "Между тем, слои в которых создается маска (напр. `Embedding`) предлагают метод `compute_mask(input, previous_mask)` который вы можете вызвать.\n",
        "\n",
        "Поэтому вы можете сделать что-то наподобие этого:\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "coCV26fqXmya"
      },
      "outputs": [],
      "source": [
        "class MyLayer(layers.Layer):\n",
        "  \n",
        "  def __init__(self, **kwargs):\n",
        "    super(MyLayer, self).__init__(**kwargs)\n",
        "    self.embedding = layers.Embedding(input_dim=5000, output_dim=16, mask_zero=True)\n",
        "    self.lstm = layers.LSTM(32)\n",
        "    \n",
        "  def call(self, inputs):\n",
        "    x = self.embedding(inputs)\n",
        "    # Отметим, что вы можете также приготовить `mask` тензор вручную.\n",
        "    # Необходимо только чтобы он был булевым тензором\n",
        "    # правильной размерности, т.е. (batch_size, timesteps).\n",
        "    mask = self.embedding.compute_mask(inputs)\n",
        "    output = self.lstm(x, mask=mask)  # Слой будет игнорировать маскированные значения\n",
        "    return output\n",
        "\n",
        "layer = MyLayer()\n",
        "x = np.random.random((32, 10)) * 100\n",
        "x = x.astype('int32')\n",
        "layer(x)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "uSZP15mtWs9Z"
      },
      "source": [
        "## Поддержка масок в ваших кастомных слоях"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "w2gg7O3kVjC4"
      },
      "source": [
        "Вам может понадобиться написать слои, которые генерируют маску (например,` Embedding`), или слои, которым нужно изменить текущую маску.\n",
        "\n",
        "Например, любой слой производящщий тензор с отличающейся от входа измерением времени, такой как слой `Concatenate` который соединяет по времени, будет нуждаться в модификации текущей маски чтобы нижележащие слои могли должным образом учитывать маскированные временные шаги.\n",
        "\n",
        "Чтобы сделать это ваш слой должен реализовать метод `layer.compute_mask()`, который производит новую маску с учетом входных данных и текущей маски. \n",
        "\n",
        "Большинство слоев не меняет размерность таймсерий, поэтому вам не нужно переживать о масках. По умолчанию, в таких случаях `compute_mask()` просто передает дальше текущую маску.\n",
        "\n",
        "Вот пример слоя `TemporalSplit` требующего модификации текущей маски."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "gaS_7dXyr-Z0"
      },
      "outputs": [],
      "source": [
        "class TemporalSplit(tf.keras.layers.Layer):\n",
        "  \"\"\"Разобьем входной тензор на 2 тензора по временной оси.\"\"\"\n",
        "\n",
        "  def call(self, inputs):\n",
        "    # Ожидается трехмерный вход, а маска должна быть двумерной, разобьем входной тензор на 2\n",
        "    # подтензора вдоль временной оси (ось 1).\n",
        "    return tf.split(inputs, 2, axis=1)\n",
        "    \n",
        "  def compute_mask(self, inputs, mask=None):\n",
        "    # Также разобьем маску на 2 если она есть.\n",
        "    if mask is None:\n",
        "      return None\n",
        "    return tf.split(mask, 2, axis=1)\n",
        "\n",
        "first_half, second_half = TemporalSplit()(masked_embedding)\n",
        "print(first_half._keras_mask)\n",
        "print(second_half._keras_mask)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "5t73OUJgjLLW"
      },
      "source": [
        "Вот еще один пример слоя `CustomEmbedding` способного генерировать маску из входных значений:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "fLSpf1iojSO7"
      },
      "outputs": [],
      "source": [
        "class CustomEmbedding(tf.keras.layers.Layer):\n",
        "  \n",
        "  def __init__(self, input_dim, output_dim, mask_zero=False, **kwargs):\n",
        "    super(CustomEmbedding, self).__init__(**kwargs)\n",
        "    self.input_dim = input_dim\n",
        "    self.output_dim = output_dim\n",
        "    self.mask_zero = mask_zero\n",
        "    \n",
        "  def build(self, input_shape):\n",
        "    self.embeddings = self.add_weight(\n",
        "      shape=(self.input_dim, self.output_dim),\n",
        "      initializer='random_normal',\n",
        "      dtype='float32')\n",
        "    \n",
        "  def call(self, inputs):\n",
        "    return tf.nn.embedding_lookup(self.embeddings, inputs)\n",
        "  \n",
        "  def compute_mask(self, inputs, mask=None):\n",
        "    if not self.mask_zero:\n",
        "      return None\n",
        "    return tf.not_equal(inputs, 0)\n",
        "  \n",
        "  \n",
        "layer = CustomEmbedding(10, 32, mask_zero=True)\n",
        "x = np.random.random((3, 10)) * 9\n",
        "x = x.astype('int32')\n",
        "\n",
        "y = layer(x)\n",
        "mask = layer.compute_mask(x)\n",
        "\n",
        "print(mask)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "fUopC-DelkG2"
      },
      "source": [
        "## Написание слоев которые нуждаются в информации о маске\n",
        "\n",
        "Некоторые слои являются *потребителями* масок: у них есть аргумент `mask` в `call` используемый для определения временных шагов которые нужно пропустить.\n",
        "\n",
        "Для написания такого слоя, вы можете просто добавить аргумент `mask=None` в сигнатуру вызова `call`. Маска связанная с входными данными будет передана вашему слою, когда она будет доступной.\n",
        "\n",
        "```python\n",
        "class MaskConsumer(tf.keras.layers.Layer):\n",
        "  \n",
        "  def call(self, inputs, mask=None):\n",
        "    ...\n",
        "```"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "_50qkZjIn8b2"
      },
      "source": [
        "## Повторение\n",
        "\n",
        "Это все что вам нужно знать о масках в Keras. Повторим:\n",
        "\n",
        "- \"Маскировка\" это то, как слои могут узнавать когда пропускать / игнорировать конкретные временные шаги в последовательных временных данных.\n",
        "- Некоторые слои являются генераторами масок: `Embedding` может генерировать маску из входных данных (если `mask_zero=True`), как и слой `Masking`.\n",
        "- Некоторые слои являются потребителями масок: они используют аргумент `mask` в своем методе `__call__`. Это относится к слоям RNN.\n",
        "- В Functional API и Sequential API, информация маски распространяется автоматически.\n",
        "- При написании наследуемых моделей или при использованнии слоев в автономном режиме, передайте аргументы `mask` слоям вручную.\n",
        "- Вы можете легко написать слои изменяющие текущую маску, генерирующие новую маску, или использующие маску связанную с входными данными.\n"
      ]
    }
  ],
  "metadata": {
    "colab": {
      "collapsed_sections": [],
      "name": "masking_and_padding.ipynb",
      "toc_visible": true
    },
    "kernelspec": {
      "display_name": "Python 3",
      "name": "python3"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}
